// Parser.h
//
// Parseador de XML que usa la libreria expatpp para crear un arbol
// con la estructura de un archivo XML.
// Con el mtodo toString convierte el arbol en un string para
// llevarlo a un archivo.
//////////////////////////////////////////////////////////////////////
/*

USO DE EXPATPP :
===============

(orientado a objetos del parseador de XML expat libreria creada para C).

expatpp follows a simple pattern for converting the semi-OOP callback design of 
expat into a true class which allows you to override virtual methods to supply
callbacks.

USING expatpp
see testexpatpp.cpp for a detailed example

1) decide which callbacks you wish to use, eg: just startElement

2) declare a subclass of expatpp, eg:
class myExpat : public expatpp {
	virtual void startElement(const XML_Char* name, const XML_Char** atts);
};

3) create an instance of your object and pass in a buffer to parse
myExpat parser;
parser.XML_Parse(buf, len, done)


HOW IT WORKS
The User Data which expat maintains is simply a pointer to an instance of your object.

Inline static functions are specified as the callbacks to expat.
These static functions take the user data parameter returned from expat and cast it
to a pointer to an expatpp object.

Using that typed pointer they then call the appropriate virtual method.

If you have overriden a given virtual method then your version will be called, otherwise
the (empty) method in the base expatpp class is called.

NOTE:
For efficiency, you could provide your own constructor and set some of the callbacks
to 0, so expat doesn't call the static functions. (untested idea).

*/
#if !defined(AFX_PARSER_H__4F056C47_9725_4820_9603_6C5DEC06BFCD__INCLUDED_)
#define AFX_PARSER_H__4F056C47_9725_4820_9603_6C5DEC06BFCD__INCLUDED_

//#pragma warning(disable:4786)

#if _MSC_VER > 1000
	#pragma once
#endif // _MSC_VER > 1000

#include "expatpp.h"
#include <list>
#include <map>
#include <string>

using namespace std;

class XML;
class XMLNode;
class XMLDoc;

typedef std::list<XMLNode*> listaXML; // defino una lista de elementos XML	  
typedef std::map<string, string> mapaAtributos; // defino un mapa de clave string y contenido string
typedef listaXML::iterator listaXML_iterator;   // defino iterador para recorrerla lista
typedef listaXML::const_iterator listaXML_const_iterator; // defino iterador para recorrerla lista pero constant


/*
XMLNode

clase que representa un nodo XML. Posee nombre, contenido, atributos e
hijos si los posee. Tiene la capasidad de cargarse y de escribirse.

*/class XMLNode{
	
	protected:
			// nombre del nodo
			string nombre;
			// contenido del nodo 
			string contenido;
			// atributos
			mapaAtributos atributos;	
			// padre si lo tiene, sino NULL
			XMLNode *padre;
	public:

		listaXML hijos;

		XMLNode(){
			nombre = "";
			contenido = "";
		};
		
		XMLNode(mapaAtributos map){
			this->atributos = map;
			nombre = "";
			contenido = "";
		};

		mapaAtributos getAtributos(){
			return this->atributos;
		};

		/*
		getContenido
		============

		Devuelve el contenido del nodo. Representa en el XML
		el string que encierra el tag <llll> contenido </llll>
		Sino posee se devuelve un string vacio.

		*/string getContenido(){
			return this->contenido;
		};
		virtual ~XMLNode(){
			
			this->atributos.erase(this->atributos.begin(), this->atributos.end());
			listaXML::iterator j = this->hijos.begin();
			 //llama al to String de cada hijo
			for(; j != this->hijos.end() ;j++){
				delete *j;
			}
		};
		//Se obtiene de mapa la value con la key asociada
		string getAtributo(string key)
		{
			mapaAtributos::iterator iter;
			iter = this->atributos.find(key);
			if (iter != this->atributos.end()) 
				return iter->second;
			else
				return "";
		}
		/*
		cargarAtributos
		
		carga los atributos y el nombre del nodo. Recibe una lista
		de ellos como lo que devuelve el expatpp.

		*/void cargarAtributos(const char* name, const char** atts){
			
			this->nombre.append(name);
			if (atts) { 
				int i;
				for (i=0; atts[i]; i++) {
				  // en atts de guarda atts[i] = name ; atts[i + 1] = value 
				  	string key = ""; key.append(atts[i++]);
					string valor = ""; valor.append(atts[i]);
					this->insertarAtributo(key, valor);
				}
			}

		};
		
				/*
		setPadre

		setea el padre
		
		*/void setNombre(string nombre){
			this->nombre = nombre;
		};
		/*
		setPadre

		setea el padre
		
		*/void setPadre(XMLNode *padre){
			this->padre = padre;
		};
		/*
		getPadre

		devuelve el padre del nodo, NULL si no lo tiene.

		*/XMLNode* getPadre(){
			return (this->padre);
		}
		/*
		insertarAtributo

		inserta el par de atributos en el mapa de atributos del nodo.

		*/void insertarAtributo(string key, string valor){
			this->atributos.insert(mapaAtributos::value_type(key, valor));
		};
		/*
		setContenido

		Cambia el contenido del nodo con la cantidad pasada por length
		
		*/void setContenido(const char *contenido, int length){
			this->contenido.append(contenido, length);
		};

				/*
		setContenido

		Cambia el contenido del nodo con la cantidad pasada por length
		
		*/void setContenido(string contenido){
			this->contenido.append(contenido);
		};
		/*
		agregarHijo

		agrega un hijo al final de la lista de hijos del nodo.

		*/void agregarHijo(XMLNode *node){
			node->setPadre(this);
			this->hijos.push_back(node);
		};
		/*
		getNombre

		devuelve el nombre del nodo.

		*/string getNombre(){
			return this->nombre;		
		}
		/*
		toString
		
		convierte el arbol en un string y lo devuelve. Esta funcion
		se llama recursivamente por todos los nodos para ir creando
		el archivo XML.

		off: cantidad de espacios de tabulacion.

		*/string toString(int off) const {
		
			string ret;

			// coloca espacios para conseguir la tabulacion correcta
			for(int i=0; i<off; i++) 
			
			ret.append(" ");
			ret.append("<");  
			//arma el nombre <XXXX
			ret += this->nombre; 

			// Agrego los atributos del nodo si llega a tener.
			mapaAtributos::const_iterator j = this->atributos.begin();
			for(;j != atributos.end(); j++){
					//ret.append(" ");
					ret.append(j->first); //arma la clave
					ret.append("=\\");    //coloca el =
					ret.append(j->second); // arma el contenido
					ret.append("\\"); 
			};

				// a este punto <XXX XXX="XXX" ... XXX="XXX"
				
			// si el princio es distinto del final tiene hijos
				if(this->hijos.begin() != this->hijos.end()) 
					{
					ret.append(">\n");           
					// utiliza un iterador para recorrer la lista
					listaXML::const_iterator j = this->hijos.begin();
					 //llama al to String de cada hijo
					for(;j != this->hijos.end() ;j++)
						ret.append( (*j)->toString(off + 5) );
					for(int k=0; k<off; k++) ret.append(" ");
					ret.append("</");  // cierra el padre
					ret.append(this->nombre);
					ret.append(">\n");
					}
				else  
					{
					if(contenido != string()) //si contiene contenido
						{
						ret.append(">");    // cierra 
						ret.append(this->contenido); //coloca el contenido
						ret.append("</"); 
						ret.append(this->nombre);
						ret.append(">\n"); // crea el tag que cierra al padre
						}
					else
					ret.append("/>\n"); // caso de tag sin contenido y sin hijos
				};
			return ret; 
	};

};

/*
XMLDoc

Esta clase en su lista tendra todo el arbol de XML

*/
class XMLDoc : public XMLNode {

public:
	
	XMLDoc(){
	
	};
	virtual ~XMLDoc(){

	};
	
	/*
	toFile

	Devuelve un string donde se encuentra el archivo XML
	Recibe tab, la cantidad de espacios para tabular.

	*/string toFile(int tab){
		
		XMLNode* node = (XMLNode*)this;
		string xml;
		xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n";
		xml += node->toString(tab);
		return (xml);

	};
};


/*
parserXML

parseador de XML. Utiliza la libreria expatpp para recorrer el archivo
XML por medio de esta interface:

startElement(const XML_Char *name, const XML_Char **atts);
===========================================================
Este mtodo se llama cada vez que empiesa un nodo de XML, y trae
por parmetro el nombre con la lista de atributos. La lista de atrib
se accede atts[i] = nombre y atts[i+1] = value.


charData(const XML_Char *s, int len);
===========================================================
Este mtodo se llama cuando el nodo que se esta parseando tiene
contenido, devuelve el contenido en un char* y la longitud en len.


endElement(const XML_Char* name);
===========================================================
Este mtodo se llama cada vez que se cierra el nodo que se 
esta parseando.

*/class parserXML : public expatpp{

private:
	XMLNode *lastNode;
	XMLDoc *doc;
	
	void trim( string& str)
	{
	  string::size_type pos = str.find_last_not_of(' ');
	  if(pos !=  string::npos) { 
	    str.erase(pos + 1);
	    pos = str.find_first_not_of(' ');
	    if(pos != string::npos) str.erase(0, pos);
	  }
	  else str.erase(str.begin(), str.end());
	  
	  pos = str.find_last_not_of('\t');
	  if(pos != string::npos) {
	    str.erase(pos + 1);
	    pos = str.find_first_not_of('\t');
	    if(pos != string::npos) str.erase(0, pos);
	  }
	  else str.erase(str.begin(), str.end());
	  
	pos = str.find_last_not_of('\n');
	  if(pos != string::npos) {
	    str.erase(pos + 1);
	    pos = str.find_first_not_of('\n');
	    if(pos != string::npos) str.erase(0, pos);
	  }
	  else str.erase(str.begin(), str.end());
	  
	pos = str.find_last_not_of('\r');
	  if(pos != string::npos) {
	    str.erase(pos + 1);
	    pos = str.find_first_not_of('\r');
	    if(pos != string::npos) str.erase(0, pos);
	  }
	  else str.erase(str.begin(), str.end());
	  			  
	}

public:
	
	/*
	constructor

	crea el documento donde se guardara lo que parsee y pone como
	ultimo Nodo el documento.

	*/parserXML(){
		this->doc = new XMLDoc();
		this->lastNode = (XMLNode*)this->doc;
	};
	
	parserXML(XMLDoc* doc){
		this->doc = doc;
		this->lastNode = (XMLNode*)this->doc;
	};
	/*
	 destructor
	 
	 Borra el documento creado.

	 */virtual ~parserXML(){
		//delete this->doc;
	};
	
	/*
	startElement
	
	Toma la informacion de un nodo (nombre/atributos) y crea un nodo
	para ingresarlo en el arbol. Actualiza el ultimo nodo (lastNode) para
	que se ingresen los hijos en l.

	*/void startElement(const XML_Char *name, const XML_Char **atts){
		
		XMLNode *nodo = new XMLNode();
		
		nodo->setPadre(lastNode);
		nodo->cargarAtributos(name, atts);
		if (this->lastNode!=0)
			this->lastNode->agregarHijo(nodo);
		this->lastNode = nodo;
	
	};
	/*
	endElement

	Simplemente actualiza el ultimo nodo volciendo para "atras"
	en el arbol haciendo que el ultimo nodo sea el padre de quien
	termino.

	*/void endElement(const XML_Char* name){

	  this->lastNode = lastNode->getPadre();

	};
	/*
	charData
	
	Carga el contendio del nodo cuando este lo posee. Es llamado
	despus de startElement por eso no se actualiza lastNode.

	*/void charData(const XML_Char *s, int len){
	
//		const int leadingSpace = skipWhiteSpace(s);
//		if (len==0 || len==leadingSpace)
//			return;  // si no tiene no se carga
		
		string newStr;
		newStr.append(s, len);
		this->trim(newStr);
		if (newStr.length() > 0){
			this->lastNode->setContenido(newStr);
		}

	};
	/*
	agregarHijo

	Agrega un hijo a la estructura del arbol a partir del
	ltimo nodo (lastNode).

	*/void agregarHijo(XMLNode *nodo){
		this->lastNode->agregarHijo(nodo);
	};
	/*
	getDoc

	Devuelve el documento creado por el parser.

	*/XMLDoc* getDoc(){
		return this->doc;
	};

	
};

#endif // !defined(AFX_PARSER_H__4F056C47_9725_4820_9603_6C5DEC06BFCD__INCLUDED_)
